/**
 * 
 */
package com.dalonedrow.module.hq.systems;

import com.dalonedrow.engine.sprite.base.SimpleDimension;
import com.dalonedrow.engine.sprite.base.SimplePoint;
import com.dalonedrow.engine.sprite.base.SimpleVector2;
import com.dalonedrow.engine.sprite.base.SimpleVector3;
import com.dalonedrow.engine.sprite.base.SpriteBase;
import com.dalonedrow.engine.sprite.impl.BasicQuadSpriteObject;
import com.dalonedrow.engine.systems.JOGLErrorHandler;
import com.dalonedrow.engine.systems.Render;
import com.dalonedrow.engine.systems.Time;
import com.dalonedrow.engine.systems.base.Interactive;
import com.dalonedrow.engine.systems.base.Interface;
import com.dalonedrow.engine.systems.base.Particle;
import com.dalonedrow.engine.ui.impl.Label;
import com.dalonedrow.engine.ui.impl.UIFactory;
import com.dalonedrow.globalvariables.constants.hq.GlobalConstants;
import com.dalonedrow.module.hq.bus.item.valuables.Gem;
import com.dalonedrow.module.hq.rpg.HQInteractiveObject;
import com.dalonedrow.module.hq.rpg.HQTerrainTypes;
import com.dalonedrow.module.hq.rpg.UltimaTileCell;
import com.dalonedrow.module.hq.ui.views.DungeonScreenView;
import com.dalonedrow.pooled.PooledStringBuilder;
import com.dalonedrow.pooled.StringBuilderPool;
import com.dalonedrow.utils.UtilityMethods;

/**
 * Controller class for handling IO movement and actions on the game map.
 * @author Donald
 */
public final class HQMovement {
	/** the one and only instance of the <code>HQMovement</code> class. */
	private static HQMovement	instance;
	private static final float	TIME_SEARCH_TICKER				= 2000;
	private static final float	TIME_SEARCH_TICKER_FREEZE		= 500;
	private static final float	TIME_SEARCH_ZOOM_TO_IO			= 500;
	private static final float	TIME_SEARCH_ZOOM_TO_TREASURE	= 1000;
	private static final float	TIME_SEARCH_TICKER_TREASURE	= 1500;
	/**
	 * Gives access to the singleton instance of {@link HQMovement}.
	 * @return {@link HQMovement}
	 */
	public static HQMovement getInstance() {
		if (HQMovement.instance == null) {
			HQMovement.instance = new HQMovement();
		}
		return HQMovement.instance;
	}
	/**
	 * the flag indicating whether the PC's turn should end after performing a
	 * search.
	 */
	private boolean			endTurnAfterSearch;
	/** the list of IOs that are actively traveling on the map. */
	private int[]			iosInMotion;
	/** the label displaying the search "ticker". */
	private Label			lblSearchTicker;
	/** the number of IOs that are actively traveling. */
	private int				numIOsInMotion;
	/** the id of the room being opened. */
	private int				roomOpening			= -1;
	/**
	 * flag indicating whether the IO discovered during a room search has been
	 * revealed.
	 */
	private boolean			searchIOAdded		= false;
	private boolean			searchTickerFrozen	= false;
	/** the list of tile sprites. */
	private SpriteBase[]	spritesForZoom;
	private int				treasureIO;
	/**
	 * flag indicating that the search zoom animation has been locked on the PC.
	 */
	private boolean			zoomFrozenOnPC		= false;
	/**
	 * flag indicating that the search zoom animation has been locked on the PC.
	 */
	private boolean			zoomFrozenOnIO		= false;
	/** Hidden constructor. */
	private HQMovement() {}
	/**
	 * Adds an IO to the list of IOs that are actively traveling on the map. If
	 * the IO is already on the list nothing is done; it won't be added twice.
	 * @param ioid the IO's reference id
	 */
	public void addIO(final int ioid) {
		if (iosInMotion == null) {
			iosInMotion = new int[0];
			numIOsInMotion = 0;
		}
		boolean found = false;
		for (int i = 0; i < numIOsInMotion; i++) {
			if (iosInMotion[i] == ioid) {
				found = true;
				break;
			}
		}
		if (!found) {
			int[] dest =
					new int[Math.max(iosInMotion.length, numIOsInMotion) + 1];
			System.arraycopy(iosInMotion, 0, dest, 0, numIOsInMotion);
			dest[numIOsInMotion] = ioid;
			iosInMotion = dest;
			dest = null;
			numIOsInMotion++;
		}
	}
	/**
	 * Appends the bang to the search ticker's text.
	 * @param sb the {@link } containing the text
	 * @param tickerDiff the time in milliseconds since the ticker started
	 */
	private void appendSearchTickerBangs(final PooledStringBuilder sb,
			final float tickerDiff) {
		float twentieth = HQMovement.TIME_SEARCH_TICKER / GlobalConstants.INT_020;
		if (tickerDiff / twentieth < 1) {
			sb.append("|");
		} else if (tickerDiff / twentieth < 2) {
			sb.append("/");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_003) {
			sb.append("-");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_004) {
			sb.append("\\");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_005) {
			sb.append("|");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_006) {
			sb.append("/");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_007) {
			sb.append("-");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_008) {
			sb.append("\\");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_009) {
			sb.append("|");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_010) {
			sb.append("/");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_011) {
			sb.append("-");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_012) {
			sb.append("\\");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_013) {
			sb.append("|");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_014) {
			sb.append("/");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_015) {
			sb.append("-");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_016) {
			sb.append("\\");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_017) {
			sb.append("|");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_018) {
			sb.append("/");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_019) {
			sb.append("-");
		} else if (tickerDiff / twentieth < GlobalConstants.INT_020) {
			sb.append("\\");
		}
	}
	/**
	 * Appends the dots to the search ticker's text.
	 * @param sb the {@link } containing the text
	 * @param tickerDiff the time in milliseconds since the ticker started
	 */
	private void appendSearchTickerDots(final PooledStringBuilder sb,
			final float tickerDiff) {
		float twelfth = HQMovement.TIME_SEARCH_TICKER / GlobalConstants.INT_012;
		if (tickerDiff / twelfth < 1) {
			sb.append("   ");
		} else if (tickerDiff / twelfth < 2) {
			sb.append(".  ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_003) {
			sb.append(".. ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_004) {
			sb.append("...");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_005) {
			sb.append("   ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_006) {
			sb.append(".  ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_007) {
			sb.append(".. ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_008) {
			sb.append("...");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_009) {
			sb.append("   ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_010) {
			sb.append(".  ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_011) {
			sb.append(".. ");
		} else if (tickerDiff / twelfth < GlobalConstants.INT_012) {
			sb.append("...");
		}
	}
	/**
	 * Appends the dots to the search ticker's text.
	 * @param sb the {@link } containing the text
	 * @param tickerDiff the time in milliseconds since the ticker started
	 */
	private void appendSearchTickerBling(final PooledStringBuilder sb,
			final float tickerDiff) {
		System.out.println("**appendSearchTickerBling("+tickerDiff);
		float sixth = 
			HQMovement.TIME_SEARCH_TICKER_TREASURE / GlobalConstants.INT_006;
		if (tickerDiff / sixth < 1) {
			sb.append("             ");
		} else if (tickerDiff / sixth < 2) {
			sb.append("   *bling*   ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_003) {
			sb.append("             ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_004) {
			sb.append(" *bling*     ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_005) {
			sb.append("             ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_006) {
			sb.append("     *bling* ");
		}
		System.out.println("sb::"+sb.toString()+"::");
	}
	/**
	 * Appends the dots to the search ticker's text.
	 * @param sb the {@link } containing the text
	 * @param tickerDiff the time in milliseconds since the ticker started
	 */
	private void appendSearchTickerBubbles(final PooledStringBuilder sb,
			final float tickerDiff) {
		final int three = 3, four = 4, five = 5, six = 6;;
		float sixth = 
			HQMovement.TIME_SEARCH_TICKER_TREASURE / GlobalConstants.INT_006;
		if (tickerDiff / sixth < 1) {
			sb.append("      o      ");
		} else if (tickerDiff / sixth < 2) {
			sb.append("   *bubble*  ");
		} else if (tickerDiff / sixth < three) {
			sb.append("   o         ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_004) {
			sb.append(" *pop*       ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_005) {
			sb.append("         o   ");
		} else if (tickerDiff / sixth < GlobalConstants.INT_006) {
			sb.append("      *fizz* ");
		}
	}
	/**
	 * Appends the dots to the search ticker's text.
	 * @param sb the {@link } containing the text
	 * @param tickerDiff the time in milliseconds since the ticker started
	 */
	private void appendSearchTickerClink(final PooledStringBuilder sb,
			final float tickerDiff) {
		final int three = 3;
		float third = 
			HQMovement.TIME_SEARCH_TICKER_TREASURE / three;
		if (tickerDiff / third < 1) {
			sb.append("             ");
		} else if (tickerDiff / third < 2) {
			sb.append("   *clink*   ");
		} else if (tickerDiff / third < three) {
			sb.append("             ");
		}
	}
	/**
	 * Does an animated search ticker.
	 * @param diff the time in milliseconds since the ticker appeared
	 * @throws Exception if an error occurs
	 */
	private void doAnimatedSearchTicker(final float diff) throws Exception {
		if (lblSearchTicker == null) {
			lblSearchTicker =
					UIFactory.getInstance().getStandardH2Label(
							"Searching...!");
		}
		lblSearchTicker.setVisible(true);
		// position the search ticker
		positionSearchTicker(HQController.getInstance().getPcId());
		// time the text
		PooledStringBuilder sb =
				StringBuilderPool.getInstance().getStringBuilder();
		sb.append("Searching");
		appendSearchTickerDots(sb, diff);
		appendSearchTickerBangs(sb, diff);
		lblSearchTicker.setText(sb.toString());
		sb.returnToPool();
		// display the search ticker
		for (int i = 0; i < spritesForZoom.length; i++) {
			Render.getInstance().registerEntity(spritesForZoom[i]);
		}
		lblSearchTicker.prepareForRendering();
	}
	/**
	 * Does an animated search ticker.
	 * @param diff the time in milliseconds since the ticker appeared
	 * @throws Exception if an error occurs
	 */
	private void doAnimatedTreasureTicker(final float diff) throws Exception {
		System.out.println("**doAnimatedTreasureTicker");
		lblSearchTicker.setVisible(true);
		// position the search ticker
		positionSearchTicker(treasureIO);
		System.out.println("search ticker positioned::"+lblSearchTicker.getPosition());
		// time the text
		PooledStringBuilder sb =
				StringBuilderPool.getInstance().getStringBuilder();
		// determine text
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(
						this.treasureIO);
		System.out.println("io is in "+io.getNumIOGroups());
		if (io.isInGroup("JEWELRY")) {
			appendSearchTickerBubbles(sb, diff);
		}
		lblSearchTicker.setText(sb.toString());
		sb.returnToPool();
		System.out.println("search ticker text::"+lblSearchTicker.getText());
		// display the search ticker
		for (int i = 0; i < spritesForZoom.length; i++) {
			Render.getInstance().registerEntity(spritesForZoom[i]);
		}
		lblSearchTicker.prepareForRendering();
	}
	/**
	 * Does the "zooming" animation at the beginning of the "search for
	 * treasure" action.
	 * @param diff the number of milliseconds since the action started
	 * @throws Exception if an error occurs
	 */
	private void doAnimatedSearchZoomToPC(final float diff) throws Exception {
		final int three = 3, four = 4;
		if (spritesForZoom == null) {
			spritesForZoom = new SpriteBase[four];
			for (int i = 0; i < spritesForZoom.length; i++) {
				spritesForZoom[i] = new BasicQuadSpriteObject();
				spritesForZoom[i].setColor(GlobalConstants.COL_DB_BLACK1);
			}
		}
		// add an overlay that zooms in on IO.
		// get the IO's screen position
		SimpleVector3 v =
				getIOVector(HQController.getInstance().getPcId(), 0);
		int top =
				(int) (HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
						+ GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE
					- (v.getY() + GlobalConstants.IMG_SIZE));
		top = Math.abs(top);
		int topH = (int) (top * diff / HQMovement.TIME_SEARCH_ZOOM_TO_IO);
		// draw a black rectangle topH height, full width
		spritesForZoom[0].setWidth(GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE);
		spritesForZoom[0].setHeight(topH);
		spritesForZoom[0].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().right,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
						+ GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE - topH,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		int bottom =
				(int) (HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
					- v.getY());
		bottom = Math.abs(bottom);
		int bottomH = (int) (bottom * diff / HQMovement.TIME_SEARCH_ZOOM_TO_IO);
		// draw a black rectangle bottomH height, full width
		spritesForZoom[1].setWidth(GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE);
		spritesForZoom[1].setHeight(bottomH);
		spritesForZoom[1].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		int right =
				(int) (HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
					- (v.getX() + GlobalConstants.IMG_SIZE));
		right = Math.abs(right);
		int rightW = (int) (right * diff / HQMovement.TIME_SEARCH_ZOOM_TO_IO);
		// draw a black rectangle rightW width, full height
		spritesForZoom[2].setHeight(GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE);
		spritesForZoom[2].setWidth(rightW);
		spritesForZoom[2].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().right
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
						- rightW,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		int left =
				(int) (HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left
					- v.getX());
		left = Math.abs(left);
		int leftW = (int) (left * diff / HQMovement.TIME_SEARCH_ZOOM_TO_IO);
		// draw a black rectangle rightW width, full height
		spritesForZoom[three].setHeight(
				GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE);
		spritesForZoom[three].setWidth(leftW);
		spritesForZoom[three].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		for (int i = 0; i < spritesForZoom.length; i++) {
			Render.getInstance().registerEntity(spritesForZoom[i]);
		}
	}
	/**
	 * Does the "zooming" animation in the middle of the "search for treasure"
	 * action.
	 * @param diff the number of milliseconds since the action started
	 * @throws Exception if an error occurs
	 */
	private void doAnimatedSearchZoomToTreasure(final float diff)
			throws Exception {
		final int three = 3;
		float pct = diff / HQMovement.TIME_SEARCH_ZOOM_TO_TREASURE;
		// add an overlay that zooms in on IO.
		// get the IO's screen position
		SimpleVector3 vPC =
				getIOVector(HQController.getInstance().getPcId(), 0);
		// get the treasure's screen position
		SimpleVector3 vIO = getIOVector(treasureIO, 0);
		// how far does the top overlay need to travel?
		float pcSideEnd = (float) (vPC.getY() + GlobalConstants.IMG_SIZE);
		float ioSideEnd = (float) (vIO.getY() + GlobalConstants.IMG_SIZE);
		int sideLength =
				(int) (HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
						+ GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE
					- (vPC.getY() + GlobalConstants.IMG_SIZE));
		sideLength = Math.abs(sideLength);
		sideLength += (pcSideEnd - ioSideEnd) * pct;
		spritesForZoom[0].setHeight(sideLength);
		spritesForZoom[0].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().right,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
						+ GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE
						- sideLength,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		// bottom
		pcSideEnd = (float) vPC.getY();
		ioSideEnd = (float) vIO.getY();
		sideLength =
				(int) (HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
					- vPC.getY());
		sideLength = Math.abs(sideLength);
		sideLength += (ioSideEnd - pcSideEnd) * pct;
		spritesForZoom[1].setHeight(sideLength);
		// RIGHT
		pcSideEnd = (float) (vPC.getX() + GlobalConstants.IMG_SIZE);
		ioSideEnd = (float) (vIO.getX() + GlobalConstants.IMG_SIZE);
		sideLength =
				(int) (HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
					- (vPC.getX() + GlobalConstants.IMG_SIZE));
		sideLength = Math.abs(sideLength);
		sideLength += (pcSideEnd - ioSideEnd) * pct;
		spritesForZoom[2].setWidth(sideLength);
		spritesForZoom[2].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().right
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
						- sideLength,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		// LEFT
		pcSideEnd = (float) vPC.getX();
		ioSideEnd = (float) vIO.getX();
		sideLength =
				(int) (HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left
					- vPC.getX());
		sideLength = Math.abs(sideLength);
		sideLength += (ioSideEnd - pcSideEnd) * pct;
		spritesForZoom[three].setWidth(sideLength);

		for (int i = 0; i < spritesForZoom.length; i++) {
			Render.getInstance().registerEntity(spritesForZoom[i]);
		}
	}
	/**
	 * Fixes the "zooming animation around the PC.
	 * @throws Exception if an error occurs
	 */
	private void fixZoomOnIO(final int ioid) throws Exception {
		final int three = 3;
		// add an overlay that zooms in on IO.
		// get the IO's screen position
		SimpleVector3 v = getIOVector(ioid, 0);
		int top =
				(int) (HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
						+ GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE
					- (v.getY() + GlobalConstants.IMG_SIZE));
		top = Math.abs(top);
		// draw a black rectangle topH height, full width
		spritesForZoom[0].setWidth(GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE);
		spritesForZoom[0].setHeight(top);
		spritesForZoom[0].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().right,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
						+ GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE - top,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		int bottom =
				(int) (HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom
					- v.getY());
		bottom = Math.abs(bottom);
		// draw a black rectangle bottomH height, full width
		spritesForZoom[1].setWidth(GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE);
		spritesForZoom[1].setHeight(bottom);
		spritesForZoom[1].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		int right =
				(int) (HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
					- (v.getX() + GlobalConstants.IMG_SIZE));
		right = Math.abs(right);
		// draw a black rectangle rightW width, full height
		spritesForZoom[2].setHeight(GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE);
		spritesForZoom[2].setWidth(right);
		spritesForZoom[2].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().right
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
						- right,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
		int left =
				(int) (HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left
					- v.getX());
		left = Math.abs(left);
		// draw a black rectangle rightW width, full height
		spritesForZoom[three].setHeight(
				GlobalConstants.MAP_HEIGHT * GlobalConstants.IMG_SIZE);
		spritesForZoom[three].setWidth(left);
		spritesForZoom[three].setPosition(
				HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder().getInsets().left,
				HQMap.getInstance().getPosition().getY()
						+ HQMap.getInstance().getBorder().getInsets().bottom,
				HQMap.getInstance().getPosition().getZ()
						+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1);
	}
	/**
	 * Gets the screen position of an IO that is moving along a path.
	 * @param ioid the IO's reference id
	 * @param zMod the modifier to the IO's z-level
	 * @return {@link SimpleVector3}
	 * @throws Exception if an error occurs
	 */
	private SimpleVector3 getAnimatedIOMove(final int ioid, final float zMod)
			throws Exception {
		SimpleVector3 v = null;
		// x starts out at the left-side of the widget
		float x = (float) HQMap.getInstance().getPosition().getX();
		if (HQMap.getInstance().getBorder() != null) {
			x += HQMap.getInstance().getBorder().getInsets().left;
		}
		// y starts out at the bottom-side of the widget
		float y = (float) HQMap.getInstance().getPosition().getY();
		if (HQMap.getInstance().getBorder() != null) {
			y += HQMap.getInstance().getBorder().getInsets().bottom;
		}
		float z = (float) HQMap.getInstance().getPosition().getZ();
		z += (float) GlobalConstants.CHILD_STEP * zMod;
		// get the IO
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(
						ioid);
		// get the IO's map location
		SimplePoint pt = UtilityMethods.getInstance().convertIntToPoint(
				HQMap.getInstance().getIOMapLocation(ioid));
		int cellX = (int) pt.getX();
		int cellY = (int) pt.getY();
		// check the time
		long now = Time.getInstance().getFrameStart();
		long animLastUpdate =
				io.getScript().getLocalVariable("moveLastUpdate").getLongVal();
		boolean animPaused =
				io.getScript().getLocalVariable("movePaused").getIntVal() > 0;
		long diff = now - animLastUpdate;
		if (animPaused) {
			// has it been 250ms since last update?
			long maxdiff =
					UtilityMethods.getInstance().convertMilliToNano(150);
			if (diff >= maxdiff) {
				// time to move again on next cycle
				animPaused = false;
				io.getScript().setLocalVariable("movePaused", new Integer(0));
				// reset the last update
				animLastUpdate = now;
				io.getScript().setLocalVariable(
						"moveLastUpdate", new Long(now));
			}
			HQMap.getInstance().centerMapOnCell(cellX, cellY);
			// just draw the IO on the map
			// add cell's column position
			x += cellX * HQMap.getInstance().getTileMapCellSize();
			// add cell's row position
			y += cellY * HQMap.getInstance().getTileMapCellSize();
			// adjust for viewport offset
			x -= HQMap.getInstance().getViewX();
			y -= HQMap.getInstance().getViewY();
			v = new SimpleVector3(x, y, z);
		} else {
			// not paused. draw moving
			// check to see where the IO is, and move it 1 tile every 250ms
			// where is io right now?
			int destLoc =
					io.getScript().getLocalVariable("ioCurrentDest")
							.getIntVal();
			SimplePoint targ =
					UtilityMethods.getInstance().convertIntToPoint(destLoc);
			// has it been 350ms since last update?
			long maxdiff =
					UtilityMethods.getInstance().convertMilliToNano(350);
			if (diff >= maxdiff) {
				// reset the last update
				animLastUpdate = now;
				io.getScript().setLocalVariable(
						"moveLastUpdate", new Long(now));
				// IO made it to next tile.
				if (!isIOAtFinalDestination(io)) {
					HQMap.getInstance().moveIOToLocation(ioid, destLoc);
				}
				cellX = (int) targ.getX();
				cellY = (int) targ.getY();
				// is the next tile the final destination?
				if (isIOAtFinalDestination(io)) {
					int b = io.getScript().getLocalVariable(
							"playedEndTravelPause").getIntVal();
					boolean playedEndTravelPause = b > 0;
					if (playedEndTravelPause) {
						stopIOTravel(ioid);
					} else {
						animPaused = true;
						io.getScript().setLocalVariable(
								"movePaused", new Integer(1));
						playedEndTravelPause = true;
						io.getScript().setLocalVariable(
								"playedEndTravelPause", new Integer(1));
					}
				} else {
					// pause for a second and start moving again
					animPaused = true;
					io.getScript().setLocalVariable(
							"movePaused", new Integer(1));
					// set next target in path
					io.getScript().setLocalVariable(
							"ioCurrentDest", getNextTileInPath(ioid));
				}
				// just draw the IO on the map
				// add cell's column position
				x += cellX * HQMap.getInstance().getTileMapCellSize();
				// add cell's row position
				y += cellY * HQMap.getInstance().getTileMapCellSize();
				// adjust for viewport offset
				x -= HQMap.getInstance().getViewX();
				y -= HQMap.getInstance().getViewY();
				v = new SimpleVector3(x, y, z);
				HQMap.getInstance().centerMapOnIO(ioid);
			} else {
				// IO still moving. calculate new position
				// new x-pos modified by (new pos - old pos) * cell size
				float xMod = (float) (targ.getX() - pt.getY())
						* HQMap.getInstance().getTileMapCellSize();
				xMod *= (float) diff / (float) maxdiff;
				// new y-pos modified by (new pos - old pos) * cell size
				float yMod = (float) (targ.getX() - pt.getY())
						* HQMap.getInstance().getTileMapCellSize();
				yMod *= (float) diff / (float) maxdiff;
				// adjust IO's position based on map position and mods
				x += cellX * HQMap.getInstance().getTileMapCellSize();
				x += xMod;
				// add cell's row position
				y += cellY * HQMap.getInstance().getTileMapCellSize();
				y += yMod;
				// adjust for viewport offset
				x -= HQMap.getInstance().getViewX();
				y -= HQMap.getInstance().getViewY();
				v = new SimpleVector3(x, y, z);
			}
		}
		pt = null;
		return v;
	}
	/**
	 * Gets the screen position of a specific IO.
	 * @param ioid the IO's reference id
	 * @param zMod any modification the the z-axis applied
	 * @return {@link SimpleVector3}
	 * @throws Exception if an error occurs
	 */
	public SimpleVector3 getIOVector(final int ioid, final float zMod)
			throws Exception {
		SimpleVector3 v = null;
		// x starts out at the left-side of the widget
		float x = (float) HQMap.getInstance().getPosition().getX();
		if (HQMap.getInstance().getBorder() != null) {
			x += HQMap.getInstance().getBorder().getInsets().left;
		}
		// y starts out at the bottom-side of the widget
		float y = (float) HQMap.getInstance().getPosition().getY();
		if (HQMap.getInstance().getBorder() != null) {
			y += HQMap.getInstance().getBorder().getInsets().bottom;
		}
		float z = (float) HQMap.getInstance().getPosition().getZ();
		z += (float) GlobalConstants.CHILD_STEP * zMod;
		// get the IO's map location
		SimplePoint pt = UtilityMethods.getInstance().convertIntToPoint(
				HQMap.getInstance().getIOMapLocation(ioid));
		int cellX = (int) pt.getX();
		int cellY = (int) pt.getY();
		pt = null;
		// check to see if it's this IO's turn
		int currIO =
				HQScript.getInstance().getGlobalIntVariableValue("ioTurn");
		if (currIO == ioid) {
			// this is the IO whose turn it is - check for movement animation
			if (isIOInMotion(ioid)) {
				v = getAnimatedIOMove(ioid, zMod);
			} else {
				// just draw the IO on the map
				// add cell's column position
				x += cellX * HQMap.getInstance().getTileMapCellSize();
				// add cell's row position
				y += cellY * HQMap.getInstance().getTileMapCellSize();
				// adjust for viewport offset
				x -= HQMap.getInstance().getViewX();
				y -= HQMap.getInstance().getViewY();
				v = new SimpleVector3(x, y, z);
			}
		} else {
			// this IO is not having a turn. just draw the IO on the map
			// add cell's column position
			x += cellX * HQMap.getInstance().getTileMapCellSize();
			// add cell's row position
			y += cellY * HQMap.getInstance().getTileMapCellSize();
			// adjust for viewport offset
			x -= HQMap.getInstance().getViewX();
			y -= HQMap.getInstance().getViewY();
			v = new SimpleVector3(x, y, z);
		}
		return v;
	}
	/**
	 * Gets the next tile location in an IO's current travel path. If there is
	 * no next location, -1 is returned.
	 * @param ioid the IO's reference id
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int getNextTileInPath(final int ioid) throws Exception {
		int nextNode = -1;
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(ioid);
		int dest = io.getScript().getLocalVariable("ioFinalDest").getIntVal();
		SimplePoint destPt = UtilityMethods.getInstance().convertIntToPoint(dest);
		int[] path = HQDijkstraSearch.getInstance().getPathToTarget(destPt);
		for (int i = 0; i < path.length; i++) {
			if (HQMap.getInstance().getIOMapLocation(ioid) == path[i]
							&& i + 1 < path.length) {
				nextNode = path[i + 1];
				break;
			}
		}
		return nextNode;
	}
	/**
	 * Determines if a traveling IO has made it to its final destination.
	 * @param io the IO instance
	 * @return true if the IO is moving and reached its final destination; false
	 *         otherwise
	 * @throws Exception if an error occurs
	 */
	private boolean isIOAtFinalDestination(final HQInteractiveObject io)
			throws Exception {
		boolean madeIt = false;
		int dest = io.getScript().getLocalVariable("ioFinalDest").getIntVal();
		if (HQMap.getInstance().getIOMapLocation(io.getRefId()) == dest) {
			madeIt = true;
		}
		return madeIt;
	}
	/**
	 * Determines if an IO is traveling on the map.
	 * @param ioid the IO's reference id
	 * @return true if the IO is traveling; false otherwise
	 */
	public boolean isIOInMotion(final int ioid) {
		boolean is = false;
		for (int i = 0; i < numIOsInMotion; i++) {
			if (iosInMotion[i] == ioid) {
				is = true;
				break;
			}
		}
		return is;
	}
	/**
	 * Opens a door at a specific location.
	 * @param doorLoc the door's location
	 */
	public void openDoor(final int doorLoc) {
		try {
			// change the cell type to open
			int open = HQTerrainTypes.getInstance().getTerrainId(
					"ultima_door_open_to_floor");
			HQMap.getInstance().setMapCellType(doorLoc, open);
			// reset movement paths
			HQMap.getInstance().resetTravelLocations();
			// find the room being opened
			int[] rooms = HQMap.getInstance().getRoomsForCell(doorLoc);
			if (rooms.length != 2) {
				SimplePoint pt =
						UtilityMethods.getInstance().convertIntToPoint(
								doorLoc);
				Exception ex = new Exception(
						"Invalid map entry. Cell "
								+ pt
								+ " leads to "
								+ rooms.length
								+ " rooms. Only 2 rooms possible.");
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
			// a door is in two rooms. find the one room the PC
			// hasn't visited yet
			HQInteractiveObject pc =
					(HQInteractiveObject) HQController.getInstance()
					.getPCObject();
			PooledStringBuilder sb =
					StringBuilderPool.getInstance()
							.getStringBuilder();
			sb.append(HQScript.getInstance()
					.getGlobalStringVariableValue(
							"currentMapName"));
			sb.append("_roomsVisited");
			int[] visited = pc.getScript().getLocalVariable(
					sb.toString()).getIntArrayVal();
			boolean room0 = true, room1 = true;
			roomOpening = rooms[1];
			for (int i = 0; i < visited.length; i++) {
				if (visited[i] == rooms[0]) {
					room0 = false;
				}
				if (visited[i] == rooms[1]) {
					room1 = false;
				}
			}
			if (room0 && room1) {
				SimplePoint pt =
						UtilityMethods.getInstance().convertIntToPoint(
								doorLoc);
				Exception ex = new Exception(
						"Invalid map entry. PC hasn't visited "
								+ "either room that Cell "
								+ pt
								+ " leads to.");
				JOGLErrorHandler.getInstance().fatalError(ex);
			} else if (!room0 && !room1) {
				/*
				 * SimpleVector2 pt = UtilityMethods.getInstance().convertIntToPoint(
				 * moc); Exception ex = new Exception(
				 * "Invalid map entry. PC has visited " +
				 * "both rooms that Cell " + pt + " leads to.");
				 * JOGLErrorHandler.getInstance().fatalError(ex);
				 */
			} else if (room0) {
				roomOpening = rooms[0];
			}
			visited = null;
			rooms = null;
			pc = null;
			sb.returnToPool();
			// open the room to view - play animation
			HQScript.getInstance().setGlobalVariable(
					"lastRoomPlayerOpened", roomOpening);
			startOpeningRoom();
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Plays the room opening animation.<br>
	 * 1. For the first 2 seconds, a particle emitter should throw fog particles
	 * over the secret door.<br>
	 * 2. after the door has been exposed, the camera should shift back to
	 * center on the PC.
	 */
	public void playAnimationRevealSecretDoors() {
		try {
			final long now = Time.getInstance().getFrameStart();
			HQInteractiveObject pc = 
					(HQInteractiveObject) HQController.getInstance()
					.getPCObject();
			// room opening. check to see if panning to room center is needed
			if (roomOpening == -1) {
				JOGLErrorHandler.getInstance().fatalError(
						new Exception("Room id being opened was never set."));
			}
			// has the panning reached completion?
			boolean needsPanning = true;
			// how long has the animation been running?
			long animStart =
					HQScript.getInstance().getGlobalLongVariableValue(
							"roomOpenStart");
			final long diff =
					UtilityMethods.getInstance().convertNanoToMilli(
							now - animStart);
			if (diff > Particle.EMITTER_MAP_FOG_LIFESPAN) {
				// room is exposed and emitter is done. pan camera to center on
				// pc
				boolean finished = HQMap.getInstance().panToIO(pc.getRefId());
				if (finished) {
					// the animation is finished
					roomOpening = -1;
					// set map mode to free
					HQMap.getInstance().setMode(
							GlobalConstants.MAP_MODE_00_FREE);
					HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_00_NONE);
					if (endTurnAfterSearch) {
						endTurnAfterSearch = false;
						HQTurn.getInstance().nextIOTurnCycle();
					}
				}
			} else if (diff > HQMap.ANIMATION_ROOM_EXPOSE_AFTER) {
				// after 450ms, copy new room to global array
				// so it appears slowly exposed
				boolean visited = HQPlayer.getInstance().hasPlayerVisitedRoom(
						pc, roomOpening);
				if (!visited) {
					HQPlayer.getInstance().addRoomVisited(pc, roomOpening);
				}
			}
			pc = null;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Plays the room opening animation. 1. For the first 2 seconds, a particle
	 * emitter should throw fog particles all over the room. 2. after the room
	 * has been exposed, the camera should shift back to center on the PC.
	 */
	public void playAnimationRoomOpening() {
		try {
			final long now = Time.getInstance().getFrameStart();
			HQInteractiveObject pc = 
					(HQInteractiveObject) HQController.getInstance()
					.getPCObject();
			// room opening. check to see if panning to room center is needed
			if (roomOpening == -1) {
				JOGLErrorHandler.getInstance().fatalError(
						new Exception("Room id being opened was never set."));
			}
			// how long has the animation been running?
			long animStart =
					HQScript.getInstance().getGlobalLongVariableValue(
							"roomOpenStart");
			final long diff =
					UtilityMethods.getInstance().convertNanoToMilli(
							now - animStart);
			if (diff > Particle.EMITTER_MAP_FOG_LIFESPAN) {
				// room is exposed and emitter is done. pan camera to center on
				// pc
				boolean finished = HQMap.getInstance().panToIO(pc.getRefId());
				if (finished) {
					// the animation is finished
					roomOpening = -1;
					// set map mode to free
					HQMap.getInstance().setMode(
							GlobalConstants.MAP_MODE_00_FREE);
					HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_00_NONE);
					// freeze controls
					Interface.getInstance().removeViewFlag(
							GlobalConstants.VW_03_CONTROLS_BLOCKED);
				}
			} else if (diff > HQMap.ANIMATION_ROOM_EXPOSE_AFTER) {
				// after 450ms, copy new room to global array
				// so it appears slowly exposed
				boolean visited = HQPlayer.getInstance().hasPlayerVisitedRoom(
						pc, roomOpening);
				if (!visited) {
					HQPlayer.getInstance().addRoomVisited(pc, roomOpening);
				}
			}
			pc = null;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Plays the room opening animation.<br>
	 * 1. For the first 1 seconds, a black screen covers the room.<br>
	 * 2. after the room has been exposed, the camera should shift back to
	 * center on the PC.
	 */
	public void playAnimationSearchForTreasure() {
		try {
			final long now = Time.getInstance().getFrameStart();
			// how long has the animation been running?
			long animStart =
					HQScript.getInstance().getGlobalLongVariableValue(
							"roomSearchStart");
			final long diff =
					UtilityMethods.getInstance().convertNanoToMilli(
							now - animStart);
			if (diff < HQMovement.TIME_SEARCH_ZOOM_TO_IO) {
				doAnimatedSearchZoomToPC(diff);
			} else if (diff < HQMovement.TIME_SEARCH_ZOOM_TO_IO
					+ HQMovement.TIME_SEARCH_TICKER) {
				if (!zoomFrozenOnPC) {
					fixZoomOnIO(HQController.getInstance().getPcId());
					zoomFrozenOnPC = true;
				}
				doAnimatedSearchTicker(
						diff - HQMovement.TIME_SEARCH_ZOOM_TO_IO);
			} else if (diff < HQMovement.TIME_SEARCH_ZOOM_TO_IO
					+ HQMovement.TIME_SEARCH_TICKER
					+ HQMovement.TIME_SEARCH_TICKER_FREEZE) {
				for (int i = 0; i < spritesForZoom.length; i++) {
					Render.getInstance().registerEntity(spritesForZoom[i]);
				}
				if (!searchTickerFrozen) {
					searchTickerFrozen = true;
					lblSearchTicker.setText("Searching...!");
				}
				lblSearchTicker.prepareForRendering();
			} else if (diff < HQMovement.TIME_SEARCH_ZOOM_TO_IO
					+ HQMovement.TIME_SEARCH_TICKER
					+ HQMovement.TIME_SEARCH_TICKER_FREEZE
					+ HQMovement.TIME_SEARCH_ZOOM_TO_TREASURE) {
				if (!searchIOAdded) {
					lblSearchTicker.setVisible(false);
					searchIOAdded = true;
					HQInteractiveObject gem =
							HQEquipment.getInstance().getFreshItem(new Gem());
					// find empty room tile
					int loc = HQMap.getInstance().getEmptyTileNearIO(
							HQController.getInstance().getPcId());
					HQMap.getInstance().addIOToMap(loc, gem.getRefId());
					treasureIO = gem.getRefId();
					gem = null;
				}
				doAnimatedSearchZoomToTreasure(
						diff - HQMovement.TIME_SEARCH_ZOOM_TO_IO 
						- HQMovement.TIME_SEARCH_TICKER 
						- HQMovement.TIME_SEARCH_TICKER_FREEZE);
			} else if (diff < HQMovement.TIME_SEARCH_ZOOM_TO_IO
					+ HQMovement.TIME_SEARCH_TICKER
					+ HQMovement.TIME_SEARCH_TICKER_FREEZE
					+ HQMovement.TIME_SEARCH_ZOOM_TO_TREASURE
					+ HQMovement.TIME_SEARCH_TICKER_TREASURE) {
				if (!zoomFrozenOnIO) {
					fixZoomOnIO(treasureIO);
					zoomFrozenOnIO = true;
				}
				doAnimatedTreasureTicker(
						diff - HQMovement.TIME_SEARCH_ZOOM_TO_IO
						- HQMovement.TIME_SEARCH_TICKER
						- HQMovement.TIME_SEARCH_TICKER_FREEZE
						- HQMovement.TIME_SEARCH_ZOOM_TO_TREASURE);
			} else {
				for (int i = 0; i < spritesForZoom.length; i++) {
					Render.getInstance().registerEntity(spritesForZoom[i]);
				}
				System.exit(1);
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Positions the search ticker above the PC.
	 * @throws Exception if an error occurs
	 */
	private void positionSearchTicker(final int ioid) throws Exception {
		SimpleDimension size = lblSearchTicker.getPreferredSize();
		// get the IO's screen position
		SimpleVector3 v = getIOVector(ioid, 0);
		// ticker positions centered above PC
		float x = (float) v.getX();
		x -= (size.width - GlobalConstants.IMG_SIZE) / 2;
		float xMin = (float) (HQMap.getInstance().getPosition().getX()
				+ HQMap.getInstance().getBorder().getInsets().left);
		if (x < xMin) {
			x = xMin;
		}
		float xMax = (float)
				(HQMap.getInstance().getPosition().getX()
						+ HQMap.getInstance().getBorder()
								.getInsets().left
						+ GlobalConstants.MAP_WIDTH * GlobalConstants.IMG_SIZE
						- size.width);
		if (x > xMax) {
			x = xMax;
		}
		float y = (float) (v.getY() + GlobalConstants.IMG_SIZE);
		float z = (float) (HQMap.getInstance().getPosition().getZ()
				+ GlobalConstants.CHILD_STEP
						* HQMap.Z_LEVEL_05_IO_HIGHLIGHTS_2);
		lblSearchTicker.setPosition(x, y, z);
	}
	/**
	 * Processes the room animation.
	 * @throws Exception if an error occurs
	 */
	private void processRoomAnimation() throws Exception {
		// the room opens in a 3-step process
		// 1. center on the room
		// 2. expose the room over 350ms
		// 3. pan back down to the player

		// is the room still being exposed?// check the time
		long now = Time.getInstance().getFrameStart();
		long animLastUpdate =
				HQScript.getInstance().getGlobalLongVariableValue(
						"roomOpenLastUpdate");
		long diff = now - animLastUpdate;
		// has it been 350ms since last update?
		long maxdiff =
				UtilityMethods.getInstance().convertMilliToNano(350);
		if (diff < maxdiff) {
			// center map on the room.
			HQMap.getInstance().centerMapOnRoom(roomOpening);
			// expose the room
		} else {}
	}
	/**
	 * Removes an IO from the list of IOs in motion. If the IO is not in motion,
	 * then it will not be removed.
	 * @param ioid the IO's reference id
	 */
	public void removeIO(final int ioid) {
		if (iosInMotion == null) {
			iosInMotion = new int[0];
			numIOsInMotion = 0;
		}
		int index = -1;
		for (int i = 0; i < numIOsInMotion; i++) {
			if (iosInMotion[i] == ioid) {
				index = i;
				break;
			}
		}
		if (index > -1) {
			numIOsInMotion--;
			if (index == 0) {
				// remove first index
				int[] dest = new int[numIOsInMotion];
				System.arraycopy(iosInMotion, 1, dest, 0, numIOsInMotion);
				iosInMotion = dest;
				dest = null;
			} else if (index == numIOsInMotion) {
				// remove the last index
				int[] dest = new int[numIOsInMotion];
				System.arraycopy(iosInMotion, 0, dest, 0, numIOsInMotion);
				iosInMotion = dest;
				dest = null;
			} else {
				// remove middle index
				int[] dest = new int[numIOsInMotion];
				// copy up until index to be removed
				System.arraycopy(iosInMotion, 0, dest, 0, index);
				// copy all after index to be removed
				System.arraycopy(iosInMotion, index + 1, dest, index,
						numIOsInMotion - index);
				iosInMotion = dest;
				dest = null;
			}
		}
	}
	/**
	 * Reveals a set of secret doors in a room.
	 * @param doors the locations of all doors revealed
	 * @param roomId the room's id
	 * @param flag flag indicating that the PC's turn should end after
	 *        performing the search.
	 */
	public void revealSecretDoors(final int[] doors, final int roomId,
			final boolean flag) {
		try {
			// change the cell types to closed doors
			int closed = HQTerrainTypes.getInstance().getTerrainId(
					"ultima_door_closed_to_floor");
			for (int i = 0; i < doors.length; i++) {
				HQMap.getInstance().setMapCellType(doors[i], closed);
			}
			roomOpening = roomId;
			// open the room to view - play animation
			HQScript.getInstance().setGlobalVariable(
					"lastRoomPlayerOpened", roomOpening);
			startRevealingSecretDoors(doors);
			endTurnAfterSearch = flag;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/** Starts the room opening sequence. */
	private void startOpeningRoom() {
		try {
			// freeze controls
			Interface.getInstance().assignViewFlag(
					GlobalConstants.VW_03_CONTROLS_BLOCKED);
			// center map on room
			HQMap.getInstance().centerMapOnRoom(roomOpening);
			// set map mode to animation
			HQMap.getInstance().setMode(
					GlobalConstants.MAP_MODE_03_MOVE_ANIMATION);
			// set start time
			HQScript.getInstance().setGlobalVariable("roomOpenStart",
					new Long(Time.getInstance().getFrameStart()));
			HQScript.getInstance().setGlobalVariable("roomOpenLastUpdate",
					new Long(Time.getInstance().getFrameStart()));
			HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_02_ROOM_OPENING);
			// start the panning timer to start immediately
			// startPanningTimer(Time.getInstance().getGameTime()
			// - GlobalConstants.PANNING_START_DELAY);
			// take 2000ms to expose room
			// add emitters on all room cells
			int roomId = HQScript.getInstance().getGlobalIntVariableValue(
					"lastRoomPlayerOpened");
			UltimaTileCell[] cells = HQMap.getInstance().getRoom(roomId);
			for (int i = 0; i < cells.length; i++) {
				// get the cell's center SimpleVector2, without adjusting for the map's
				// onscreen position
				SimpleVector2 v = HQMap.getInstance().getCellCenterPoint(
						cells[i].getX(), cells[i].getY());
				// adjust center SimpleVector2 for the viewing offset
				v.move(-HQMap.getInstance().getViewX(),
						-HQMap.getInstance().getViewY());
				Particle.getInstance().addEmitter(
						HQMap.getInstance(),
						Particle.PARTICLE_MAP_FOG,
						new SimpleVector3(v.getX(), v.getY(),
								HQMap.getInstance().getPosition().getZ()
										+ (float) GlobalConstants.CHILD_STEP
										* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1));
			}
			cells = null;
			// take 350 ms to center on the room
			// take 350 ms to expose all room cells
			// take 300 ms to pause
			// center back on the player
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Starts the sequence to reveal all secret doors in a room.
	 * @param doors the list of cell's containing the secret doors
	 */
	private void startRevealingSecretDoors(final int[] doors) {
		try {
			// center map on room
			HQMap.getInstance().centerMapOnRoom(roomOpening);
			// set map mode to animation
			HQMap.getInstance().setMode(
					GlobalConstants.MAP_MODE_03_MOVE_ANIMATION);
			// set start time
			HQScript.getInstance().setGlobalVariable("roomOpenStart",
					new Long(Time.getInstance().getFrameStart()));
			HQScript.getInstance().setGlobalVariable("roomOpenLastUpdate",
					new Long(Time.getInstance().getFrameStart()));
			HQMap.getInstance().setCurrentAnimation(
					HQMap.ANIM_03_SHOW_SECRET_DOORS);
			// start the panning timer to start immediately
			// startPanningTimer(Time.getInstance().getGameTime()
			// - GlobalConstants.PANNING_START_DELAY);
			// take 2000ms to expose room
			// add emitters on all room cells
			for (int i = 0; i < doors.length; i++) {
				// get the cell's center SimpleVector2, without adjusting for the map's
				// onscreen position
				SimpleVector2 v = HQMap.getInstance().getCellCenterPoint(doors[i]);
				// adjust center SimpleVector2 for the viewing offset
				v.move(-HQMap.getInstance().getViewX(),
						-HQMap.getInstance().getViewY());
				Particle.getInstance().addEmitter(
						HQMap.getInstance(),
						Particle.PARTICLE_MAP_FOG,
						new SimpleVector3(v.getX(), v.getY(),
								HQMap.getInstance().getPosition().getZ()
										+ (float) GlobalConstants.CHILD_STEP
										* HQMap.Z_LEVEL_04_IO_HIGHLIGHTS_1));
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	public void startSearch(final int ioid,
			final boolean endTurnAfterSearch) {
		try {
			// freeze controls
			Interface.getInstance().assignViewFlag(
					GlobalConstants.VW_03_CONTROLS_BLOCKED);
			// center map on IO
			HQMap.getInstance().centerMapOnIO(ioid);
			// draw a shrinking rectangle that zooms in on PC
			// set map mode to animation
			HQMap.getInstance().setMode(
					GlobalConstants.MAP_MODE_03_MOVE_ANIMATION);
			// set start time
			HQScript.getInstance().setGlobalVariable("roomSearchStart",
					new Long(Time.getInstance().getFrameStart()));
			HQScript.getInstance().setGlobalVariable("roomSearchLastUpdate",
					new Long(Time.getInstance().getFrameStart()));
			HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_04_SEARCH_ZOOM);
			zoomFrozenOnPC = false;
			zoomFrozenOnIO = false;
			searchTickerFrozen = false;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Stops an IO's movement.
	 * @param ioid the IO's reference id
	 * @throws Exception if an error occurs
	 */
	private void stopIOTravel(final int ioid) throws Exception {
		PooledStringBuilder sb =
				StringBuilderPool.getInstance().getStringBuilder();
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(ioid);
		int prof = io.getPCData().getProfession();
		sb.append(GlobalConstants.CLASS_TITLES[prof]);
		sb.append(" moved to ");
		SimplePoint pt = UtilityMethods.getInstance().convertIntToPoint(
				HQMap.getInstance().getIOMapLocation(ioid));
		sb.append((int) pt.getX());
		sb.append(",");
		sb.append((int) pt.getY());
		pt = null;
		sb.append(".\n");
		DungeonScreenView.getInstance().sendPlayerMessage(sb.toString());
		sb.returnToPool();
		io.getScript().setLocalVariable("ioCurrentDest", new Integer(-1));
		io.getScript().setLocalVariable("ioFinalDest", new Integer(-1));
		io.getScript().setLocalVariable("moveStart", new Long(0));
		io.getScript().setLocalVariable("moveLastUpdate", new Long(0));
		io.getScript().setLocalVariable("movePaused", new Integer(0));
		io.getScript().setLocalVariable("playedEndTravelPause", new Integer(0));
		HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_00_NONE);
		removeIO(ioid);
		// reset movement paths
		HQMap.getInstance().resetTravelLocations();
		if (ioid == HQController.getInstance().getPcId()) {
			// reset map
			HQMap.getInstance().setMode(GlobalConstants.MAP_MODE_00_FREE);
		}
	}
}
